#include <windows.h>

#include "lib.h"

#include "bjconn.h"
#include "bjrules.h"
#include "bjgame.h"
#include "bjopts.h"
#include "bjsql.h"
#include "log.h"

#include "stdncerr.h"
#include "badpacketmsgs.h"

#include "BJXMLTranslator.h"

BJConnection::BJConnection
(
	Server* server,
	SOCKET client
)
:	Connection(server, client)
{
	game = (BJGame*)0;

	minBet = 500;	    // purposefully set maxBet < minBet to disallow playing
	maxBet = 100;	    // games where these values don't come from database
	minSideBet = 500;   // kharmon.  02-13-07.  #16101.  For perfect pairs side bet.
	maxSideBet = 100;
	sideBetPaytable = 0;
	compFactor = 0;
	numDecks = 8;

	loggedOn = false;

	casinoSpecific = (void*)0;

    // kharmon.  11-16-06.  #16921.
	msglen = 0;
	memset(msgbuf,0,sizeof(msgbuf));
    #ifdef FORCEDSHOE
    m_pid="";
    #endif

}

BJConnection::~BJConnection()
{
	trackResetSavedGame();

	trackLogoutSession();

	delete game;
	game = (BJGame*)0;
	loggedOn = false;

	removeCasinoSpecific();
}

void
BJConnection::gameDone(int32 balanceAdjust)
{
	delete game;
	game = (BJGame*)0;
}

void
BJConnection::msgStart()
{
	msglen = 0;
}

void
BJConnection::msgAdd(uint8 Byte)
{
	msgbuf[msglen++] = Byte;
}

void
BJConnection::msgAdd(int32 SignedLong)
{
	msgbuf[msglen++] = (uint8)((SignedLong) & 0xff);
	msgbuf[msglen++] = (uint8)(((SignedLong) >> 8) & 0xff);
	msgbuf[msglen++] = (uint8)(((SignedLong) >> 16) & 0xff);
	msgbuf[msglen++] = (uint8)(((SignedLong) >> 24) & 0xff);
}

uint8*
BJConnection::msgGetBuf()
{
	return msgbuf + msglen;
}

void
BJConnection::msgReleaseBuf(int32 Amt)
{
	msglen += Amt;
}

void
BJConnection::msgEnd()
{
	//send the action menu flags with every message
	msglen = encode(msgbuf, obuf + oused + 1, msglen);
	msglen++;	// include msg len byte
	obuf[oused] = (uint8)(msglen & 0xff);
	oused += msglen;
}

void
BJConnection::sendStopMsg()
{
	msgStart();
	SENDBYTE('!');
	msgEnd();
}

int
BJConnection::SENDBYTE( uint8 val )
{
	msglen += Endian::SendByte( val, &msgbuf[ msglen ] );
	return msglen;
}

int
BJConnection::SENDINT4( int32 val )
{
	msglen += Endian::SendInt4( val, &msgbuf[ msglen ] );
	return msglen;
}

int
BJConnection::GetNMsgBytesUnused()
{
	int nUnused = (sizeof( msgbuf ) - msglen);
	return nUnused;
}

int
BJConnection::SENDNSTRING( char* s )
{
	if (s==NULL)
	{
		SENDBYTE(0);
		return 1;
	}

	int len = strlen(s);
	if (len >= GetNMsgBytesUnused())
		return 0;
	SENDBYTE( (uint8)len );
	for (int i=0; i<len; i++)
		SENDBYTE( s[i] );
	return len+1;
}

void BJConnection::Answer( uint8 ans )
{
	msgStart();
	SENDBYTE(ans);
	msgEnd();
}

void BJConnection::Error( int err )
{
	msgStart();
	SENDBYTE('E');
	SENDBYTE(err & 0xff);
	msgEnd();
}

// kharmon.  05-04-07.  #19358.  Return gid of bad game if there is one.
void BJConnection::Error( int err, int32 nGameNum )
{
	msgStart();
	SENDBYTE('E');
	SENDBYTE(err & 0xff);
    SENDINT4( nGameNum );
	msgEnd();
}


void
BJConnection::HandlePlayerAnsiLogin()
{
	int32 err = 0;

	if (!parseAnsiCasinoLogin(&err))
	{
		Error(err);
		return;
	}

	HandlePlayerLogin();
}

void
BJConnection::HandlePlayerUnicodeLogin()
{
	int32 err = 0;

	if (!parseUnicodeCasinoLogin(&err))
	{
		Error(err);
		return;
	}

	HandlePlayerLogin();
}

void
BJConnection::HandlePlayerLogin()	//Login
{
	int32 err = 0;

	//server must be running
	if (isServerStopping())
	{
		sendStopMsg();
		return;
	}

	//must not be logged in
	if (loggedOn)
	{
		Log::msgWarning("Login: attempt to re-log on  ");

		trackaddBadPacket(TRY_LOGON_WHEN_LOGGED_ON,msgbuf[0],(char*)msgbuf,msglen);

		Answer('N');
		return;
	}

	//
	//so the user is not logged into the casino
	//
    int32 nBadGameNum=0;        // kharmon.  05-04-07.  #19358.  Return gid of bad game if there is one.
	if (!trackHasSavedGame(&err, &nBadGameNum) && err)
	{
        if(err==ERR_BADSAVEDGAME)
        {
            Error(err, nBadGameNum);
        }
        else
        {
            Error(err);
        }	

		return;
	}

	if (!trackGetPlayerLimits(&minBet, &maxBet, &compFactor))
	{
		Error(LOGINERR_TRYLATER);
		return;
	}

	//get the player balance
	int32 balance;
	if( !trackGetBalance(&balance) )
	{
		Answer('N');
		return;
	}

    // FogBugz 1711 - GChapman
    // Check for the ruleset and return and error if you can't load it.
    //get the ruleset
	BJRules* pRules = GetRuleSet();

    if (pRules == NULL)
    {
		Error(LOGINERR_CONTACTCUSTSERVICE);
		return;
    }

	//psharpless 12/14/06 - Case: 16101
	//for Perfect Pairs we must retreive and send the side bet min/max
	//as well as the side bet paytable
	if(GetMachId() == PERFECT_PAIRS)
	{
		if(! trackGetSideBetOptions(GetMachId(), &sideBetPaytable))
		{
			Error(LOGINERR_CONTACTCUSTSERVICE);
			return;
		}

		//the side bet for Perfect Pairs will be using the exact same limits
		//as the regular blackjack bet
		minSideBet = minBet;
		maxSideBet = maxBet;
	}

	//send login confirmation message
	msgStart();

	//tag
	SENDBYTE('L');

	//balance
	SENDINT4(balance);

	//minbet
	SENDINT4(minBet);

	//maxbet
	SENDINT4(maxBet);

	//psharpless 12/14/06 - Case: 16101
	//Perfect Pairs must send down the side bet limits and paytable
	if(GetMachId() == PERFECT_PAIRS)
	{
		//minBonusBet
		SENDINT4(minSideBet);

		//maxBonusBet
		SENDINT4(maxSideBet);

		//bonus bet paytable
		SENDBYTE(sideBetPaytable);
	}

	//maxsplits
	SENDBYTE(BJGame::MAXSPLITS);

	//save game flag
	SENDBYTE(game ? 1 : 0);

	//save game
	if (game)
		game->sendState(true);

	msgEnd();

	// validate max number of subGames
    int8 nb = pRules->GetMaxBJBets();

    //offer message
	msgStart();
	SENDBYTE('O');
	uint8 actionBits = pRules->GetActionsOffered();
	SENDBYTE( actionBits );
	SENDBYTE( nb );
	msgEnd();

	//the user is now logged into the casino
	loggedOn = true;
}

void
BJConnection::HandlePlayerAction()	//Action
{
	/*
	//client request for a list of valid actions
	*/

	//must be logged on
	if (!loggedOn)
	{
		//there are no actions defined until a login occurs

		msgStart();
		SENDBYTE('A');
		SENDBYTE( 0 );	// no game actions
		SENDBYTE( 0 );	// no insurance button
		SENDBYTE( 0 );	// no noInsurance button
		msgEnd();
		return;
	}

	//must have game in progress
	if (!game)
	{
		//there is no game so the only valid action is bet/deal

		msgStart();
		SENDBYTE('A');
		SENDBYTE( 1 );	// bet/deal is the only action
		SENDBYTE( 0 );	// no insurance button
		SENDBYTE( 0 );	// no noInsurance button
		msgEnd();
		return;
	}

	// [AO 2008-01-16] HA: verify the user is still logged on
	if (!ValidateSession())
		return;

	//let the game determine the list of open actions
	game->HandlePlayerAction();
}

void
BJConnection::HandlePlayerDeal()	//Deal	//Bet
{
	//must be logged in
	if (!loggedOn)
	{
		Log::msgWarning("Deal: not logged on...invalid bet  ");

		trackaddBadPacket(TRY_DEAL_NOT_LOGGED_ON,msgbuf[0],(char*)msgbuf,msglen);

		Answer('l');
		return;
	}

	//server must be running
	if (isServerStopping())
	{
		sendStopMsg();
		return;
	}

	//must be no game in progress
	if (game)
	{
		Answer('G');
		return;
	}

	// [AO 2008-01-16] HA: verify the user is still logged on
	if (!ValidateSession())
		return;

	//get the ruleset
	BJRules* pRules = GetRuleSet();
	if (pRules == NULL)
	{
		Log::msgError("  can't load ruleset  ");
		Answer('N');
		return;
	}

	//offset
	//tag is unused, so skip it
	int k=1;
	//uint8	tag = msgbuf[k++];

	uint8	maxbets = pRules->GetMaxBJBets();

	//bet
	int32 bets[BJMAXBETS] = { 0 };
	int32 side_bets[BJMAXBETS];
    int32 totalBet = 0;

    memset(side_bets, 0, sizeof(side_bets));

	if( msgbuf[k++] != maxbets )
	{
        // apparently the server's setting has been changed
        // the client's "maxbets" info is obsolete
        // client needs to back to the lobby and start the game again
		Answer('C');
		return;
	}

    //  get player bet(s) and total bet
    uint8 i;
	for( i=0; i<maxbets; i++ )
    {
	    k += Endian::ReceiveInt4( &bets[i], &msgbuf[k] );
        totalBet += bets[i];
    }

	//psharpless 12/19/06 - Case: 16101
	//for Perfect Pairs we must retreive any bonus bets the
	//player has made
	if(m_machid == PERFECT_PAIRS)
	{
		for( i=0; i<maxbets; i++ )
		{
			k += Endian::ReceiveInt4( &side_bets[i], &msgbuf[k] );
			totalBet += side_bets[i];
		}
	}

	//get the player balance
	int32 balance = 0;          // Initialize balance in case next call fails
	trackGetBalance(&balance);

	// must be able to cover the bet(s)
	if( balance<=0 || balance<totalBet )
	{
		// no money in account
		Answer('$');
		return;
	}

	// each non Zero bet must be according to the table limits
    // there must be at least 1 bet
    bool someBet=false;
	for( i=0; i<maxbets; i++ )
    {
	    if( bets[i]>0 && (bets[i]<minBet || maxBet<bets[i]))
	    {
		    Answer('b');
		    return;
	    }
		else if(side_bets[i]>0 && (side_bets[i]<minSideBet || maxSideBet<side_bets[i]))
		{
		    Answer('b');
		    return;
		}
        else if(bets[i] > 0 ) 
            someBet = true;
            
    }

    if(! someBet )
    {
		Answer('b');
		return;
    }

	//create a new game object
	game = new BJGame(this, pRules, bets, side_bets);
	if (!game)
	{
		Log::msgError("  can't allocate game  ");
		Answer('N');
		return;
	}

    // trackNewGame will give us the bonus balance bet
    int32* pBonusBalanceBets = new int32[maxbets];
    if ( !pBonusBalanceBets )
    {
        delete game;
		game = (BJGame*)0;
		Log::msgError("  can't allocate pBonusBalanceBets  ");
		Answer('N');
		return;
    }

    // kharmon.  09-15-05. #6364.  Zero out the array of bonus balance bets
    memset(pBonusBalanceBets,0,maxbets*sizeof(*pBonusBalanceBets));

	// record the game in the database
    // For each BJBet we create a new record in BJGames table !!
    // "gid" of the record is kept in BJGame not in "pInfo" 
    int8 bet = 0;
    for( i=0; i<maxbets; i++ )
	{
        if( bets[i] )
        {
            // kharmon.  09-15-05. #6364.  pBonusBalanceBets needs to use "bet" as
            // the array offset, not "i".  
            char *gid = game->GetGID(bet);
            if( !trackNewGame(bets[i], &balance, &pBonusBalanceBets[bet++], gid, i, side_bets[i]) ) 
	        {
   		        delete game;
		        game = (BJGame*)0;
		        Log::msgError("  can't create game  ");

		        Error(LOGINERR_CONTACTCUSTSERVICE);
		        return;
	        }
        }
	}

	//save game in database in case server crashes
	trackSaveGame();

	//do the deal baby!
	if(! game->HandlePlayerDeal(pBonusBalanceBets) )
    {
		msgStart();
		SENDBYTE('E');
		msgEnd();
    }

    // Cleanup Bonus Balance Bets Array
    delete [] pBonusBalanceBets;
}

void
BJConnection::HandlePlayerHit()	//Hit
{
	// [AO 2008-01-16] HA: verify the user is still logged on
	if (!ValidateSession())
		return;

	//must have a game in progress
	if (game)
	{
		int32 balance;
		if( trackGetBalance(&balance) )
			if (game->HandlePlayerHit(balance))
				return;
	}

	trackaddBadPacket(TRY_HIT_WHEN_NO_GAME,msgbuf[0],(char*)msgbuf,msglen);

	//denied
	Answer('N');
}

void
BJConnection::HandlePlayerStand()	//Stand
{
	// [AO 2008-01-16] HA: verify the user is still logged on
	if (!ValidateSession())
		return;

	//must have a game in progress
	if (game)
	if (game->HandlePlayerStand())
		return;

	trackaddBadPacket(TRY_STAND_WHEN_NO_GAME,msgbuf[0],(char*)msgbuf,msglen);

	//denied
	Answer('N');
}

void
BJConnection::HandlePlayerDouble()	//douBle
{
	// [AO 2008-01-16] HA: verify the user is still logged on
	if (!ValidateSession())
		return;

	//must have a game in progress
	if (!game)
	{
		trackaddBadPacket(TRY_DBLDOWN_WHEN_NO_GAME,msgbuf[0],(char*)msgbuf,msglen);

		Answer('N');
		return;
	}

	//what is the total bet for the current hand
	int32 currentBet = game->GetCurrentBet();

	// Pull balance from SQL
	int32 balance;
	trackGetBalance(&balance);

	//must be able to cover the bet
	if (balance < currentBet)
	{
		Answer('$');
		return;
	}


	//do the double baby!
	if (game->HandlePlayerDouble(balance))
		return;
	else
		Answer('N');
}

void
BJConnection::HandlePlayerSplit()	//sPlit
{
	// [AO 2008-01-16] HA: verify the user is still logged on
	if (!ValidateSession())
		return;

	//must have a game object
	if (!game)
	{
		trackaddBadPacket(TRY_SPLIT_WHEN_NO_GAME,msgbuf[0],(char*)msgbuf,msglen);

		Answer('N');
		return;
	}

	//get the bet
	int bet = game->GetCurrentBet();

	//Pull balance from SQL
	int32 balance;
	trackGetBalance(&balance);

	//must be able to cover the bet
	if (balance < bet)
	{
		Answer('$');
		return;
	}


	//do the split baby!
	if (game->HandlePlayerSplit(balance))
		return;
	else
		Answer('N');
}

void
BJConnection::HandlePlayerSurrender()	//Surrender
{
	// [AO 2008-01-16] HA: verify the user is still logged on
	if (!ValidateSession())
		return;

	//must have a game in progress
	if (!game)
	{
		trackaddBadPacket(TRY_SURRENDER_WHEN_NO_GAME,msgbuf[0],(char*)msgbuf,msglen);

		Answer('N');
		return;
	}

	//Pull balance from SQL
	int32 balance;
	if( !trackGetBalance(&balance) )
	{
		Answer('N');
		return;
	}

	if (game->HandlePlayerSurrender(balance))
		return;

	//denied
	Answer('N');
}

/// [2013-07-09] Charles. Added for supporting 'Zappit Rackcard'.
void BJConnection::HandlePlayerZap()	// Zap
{
	// [AO 2008-01-16] HA: verify the user is still logged on
	if (!ValidateSession())
		return;

	//must have a game in progress
	if (!game)
	{
		trackaddBadPacket(TRY_ZAP_WHEN_NO_GAME,msgbuf[0],(char*)msgbuf,msglen);

		Answer('N');
		return;
	}

	//Pull balance from SQL
	int32 balance;
	if( !trackGetBalance(&balance) )
	{
		Answer('N');
		return;
	}

	if (game->HandlePlayerZap(balance))
		return;

	//denied
	Answer('N');
}

void
BJConnection::HandlePlayerInsurance()	//Insurance
{
	// [AO 2008-01-16] HA: verify the user is still logged on
	if (!ValidateSession())
		return;

	//must have a game in progress
	if (!game)
	{
		trackaddBadPacket(TRY_INSURANCE_WHEN_NO_GAME,msgbuf[0],(char*)msgbuf,msglen);

		Answer('N');
		return;
	}

	//offset
	//tag is unused, so just skip it
	int k=1;
	//tag
	//uint8 tag = msgbuf[k++];

	//parse the bet parameter
	int32 amt=0;
	k += Endian::ReceiveInt4( &amt, &msgbuf[k] );

	//tag
	uint8 iBJBet = msgbuf[k++];

	//get the player balance
	int32 balance;
	trackGetBalance(&balance);

	//must be able to cover the bet
	if ((amt < 0) || (balance < amt))
	{
		Answer('$');
		return;
	}

	//do the insurance baby!
	if (game->HandlePlayerInsurance(amt,iBJBet))
		return;

	Answer('N');
}

void
BJConnection::HandlePlayerUnknown()	//Unknown
{
	Log::msgWarning("Connection::getResponse:  Unknown input (0x%02x)  ",
		msgbuf[0]);
		
	trackaddBadPacket(GETREPONSE_DEFAULT_CASE,msgbuf[0],(char*)msgbuf,msglen);

	Answer('N');
}


bool8
BJConnection::getResponse()
{
	int32 ieat = 0, remain = iused, len;
	uint8* src = ibuf;

	// [AO 2007-10-01] We can send just one reply for certain repeating messages.
	// We're not handling partial buffers and incomplete messages but that's OK.
	char last_message = 0;

	while (remain > 0)
	{
		if (remain < src[0])
			break;

		// Handle an invalidly formatted message of length zero.
		if (src[0] <= 1)
		{
			ieat += 1;
			remain -= 1;
			src += 1;
			continue;
		}

        // kharmon.  11-16-06.  #16921.
	    msglen = 0;
	    memset(msgbuf,0,sizeof(msgbuf));

		len = decode(src + 1, msgbuf, src[0] - 1);
		ieat += src[0];
		remain -= src[0];
		src += src[0];

		switch (msgbuf[0])
		{
			//Signals from player to server
			case 'L':	HandlePlayerAnsiLogin	();	break;	//Login
			case 'D':	HandlePlayerDeal		();	break;	//Deal	//Bet
			case 'H':	HandlePlayerHit			();	break;	//Hit
			case 'S':	HandlePlayerStand		();	break;	//Stand
			case 'B':	HandlePlayerDouble		();	break;	//douBle
			case 'P':	HandlePlayerSplit		();	break;	//sPlit
			case 'R':	HandlePlayerSurrender	();	break;	//Surrender
			/// [2013-07-09] Charles. Added for supporting 'Zappit Rackcard'.
			case 'Z':	HandlePlayerZap(); break;	//Zap
			case 'I':	HandlePlayerInsurance	();	break;	//Insurance
			case 'A':	HandlePlayerAction		(); break;	//Actions

			case 'm':  //Balance request
				// [AO 2007-10-01] Ignore repeating messages
				// [AO 2008-01-16] HA: verify the user is still logged on
				if (msgbuf[0] != last_message && ValidateSession())
					HandleGetBalance();
				break;

			case 'M':	
			{
				switch (msgbuf[1])
				{
					case 'L':	HandlePlayerUnicodeLogin (); break;	//Login
					default:    HandlePlayerUnknown		 (); break;	//Unknown
				}
				break;
			}
			default:	HandlePlayerUnknown		();	break;	//Unknown
		}

		last_message = msgbuf[0]; // [AO 2007-10-01] Handle repeating messages
	}

	if (iused <= ieat) // [AO 2007-10-01] Avoid buffer overflow
		iused = 0;
	else
	{
		MoveMemory(ibuf, ibuf + ieat, iused - ieat);
		iused -= ieat;
	}

	return (bool8)(oused != 0);
}

// balance Broadcast System (BBS)
bool8
BJConnection::HandleGetBalance()
{
	int32 balance;

	if (isServerStopping())
	{
		sendStopMsg();
		return true;
	}

	//rule1: gotta be logged on
    if( loggedOn && trackGetBalance(&balance) )
    {
		msgStart();
        SENDBYTE('m');
        SENDINT4(balance);
        msgEnd();
    }

	//success
    return true;
}


void	
BJConnection::allocateXMLTranslator( XMLTranslator** target )
{
	*target = new BJXMLTranslator( getServer(), this );
}


// [AO 2008-01-16] Refactoring for HA
bool BJConnection::ValidateSession()
{
	int32 session_check = trackValidateSession();
	if (!session_check) // session is valid
		return true;

	// [AO 2008-01-22] Additional case from RTG 6.6.0 code drop
	if (session_check == BJSQL::NormalLogout)
		session_check = LOGINERR_NOTLOGGEDIN;

	msgStart();
	msgAdd(static_cast<uint8>('N'));
	msgAdd(static_cast<int32>(session_check));
	msgEnd();
	return false;
}

/// [2013-07-10] Charles. Added for supporting 'Zappit Rackcard'.
bool8 BJConnection::IsMachine_Zappit()
{
	if(GetMachId()== ZAPPIT) {
		return true;
	}

	return false;
}
